﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Net;
using System.IO;
using Lidgren;
using Lidgren.Network;

namespace Networking
{
    /*
     * Signal works like:
     * Packet 1: Player Key.
     * Packet 2: Signal(Header).
     * Packet 3: Signal Type(Subheader).
    */

    /*
     * Player Sync Data works like:
     * Packet 1: Player Key.
     * Packet 2: PlayerSyncData(Header).
     * Packet 3: SyncDataType(Subheader).
     * Packet 4 and higher: Data Values.
    */

    /*
     * Player New Data works like:
     * Packet 1: Player Key.
     * Packet 2: PlayerNewData(Header).
     * Packet 3: Player Count.
     * Packet 4 and higher: Player Data Properties per player.
    */

    public class Client
    {
        public string IP { get; set; }
        public int Port { get; set; }
        public LogLevel Log { get; set; }
        public NetClient NetClient;
        public NetPeerConfiguration Config;
        public delegate void JoinEventHandler(int playerkey);
        public delegate void ReceivePlayerSyncDataEventHandler(int playerkey, SyncDataType syncdatatype, NetIncomingMessage incmsg);
        public delegate void ReceiveSignalEventHandler(int playerkey, SignalType signaltype);
        public event JoinEventHandler OnJoin;
        public event ReceivePlayerSyncDataEventHandler OnReceivePlayerSyncData;
        public event ReceiveSignalEventHandler OnReceiveSignal;
        public Dictionary<int, Player> Players;
        public Player Me;

        public Client(string ip, int port, LogLevel log)
        {
            Log = log;
            IP = ip;
            Port = port;

            try
            {
                if (Log != LogLevel.None)
                {
                    Config = new NetPeerConfiguration("MyGame");
                    Message("Peer configuration initialized.");
                    NetClient = new NetClient(Config);
                    Message("Client initialized.");
                    Players = new Dictionary<int, Player>();
                    Message("Players dictionary intialized.");
                }
                else
                {
                    Config = new NetPeerConfiguration("MyGame");
                    NetClient = new NetClient(Config);
                    Players = new Dictionary<int, Player>();
                }
            }
            catch (Exception ex)
            {
                if (Log != LogLevel.None) Message(ex.ToString());
            }
        }

        public void Connect()
        {
            NetClient.Start();
            NetClient.Connect(new IPEndPoint(IPAddress.Parse(IP), Port));
        }

        public void Disconnect()
        {
            NetClient.Disconnect("Bye");
            Players = null;
        }

        public Player GetPlayerFromKey(int key)
        {
            foreach (KeyValuePair<int, Player> player in Players)
                if (player.Key == key) return player.Value;

            return null;
        }

        #region Logs
        public void Message(string text)
        {
            Console.WriteLine("CLIENT: " + text);
        }
        #endregion

        public void SendSignal(SignalType signaltype)
        {
            NetOutgoingMessage outmsg = NetClient.CreateMessage();
            outmsg.Write(Me.Key);
            outmsg.Write((byte)Headers.Signal);
            outmsg.Write((byte)signaltype);
            NetClient.SendMessage(outmsg, NetDeliveryMethod.ReliableSequenced);
            if (Log != LogLevel.None) Message("Sending signal.");
        }

        public NetOutgoingMessage BeginSendPlayerData(SyncDataType syncdatatype)
        {
            NetOutgoingMessage outmsg = NetClient.CreateMessage();
            outmsg.Write(Me.Key);
            outmsg.Write((byte)Headers.PlayerSyncData);
            outmsg.Write((byte)syncdatatype);

            return outmsg;
        }

        public void EndSendPlayerData(NetOutgoingMessage outmsg)
        {
            NetClient.SendMessage(outmsg, NetDeliveryMethod.ReliableSequenced, 0);
            if (Log != LogLevel.None) Message("Sending player data.");
        }

        public void NetUpdate()
        {
            NetIncomingMessage incmsg;

            if ((incmsg = NetClient.ReadMessage()) != null)
            {
                int playerkey;

                switch (incmsg.MessageType)
                {
                    case NetIncomingMessageType.Data:

                        playerkey = incmsg.ReadInt32();
                        Headers header = (Headers)incmsg.ReadByte();

                        switch (header)
                        {
                            case Headers.PlayerNewData:

                                if (Me == null)
                                {
                                    int playercount = incmsg.ReadInt32();

                                    if (playercount <= 1)
                                    {
                                        Player myplayer = new Player();
                                        incmsg.ReadAllProperties(myplayer);
                                        Players.Add(myplayer.Key, myplayer);
                                        Me = myplayer;
                                    }
                                    else
                                    {
                                        for (int i = 0; i < playercount; i++)
                                        {
                                            Player otherplayer = new Player();
                                            incmsg.ReadAllProperties(otherplayer);
                                            Players.Add(otherplayer.Key, otherplayer);
                                        }

                                        Me = Players[playerkey];
                                    }

                                    if (OnJoin != null) OnJoin(Me.Key);
                                }
                                else
                                {
                                    incmsg.ReadInt32();
                                    Player newplayer = new Player();
                                    incmsg.ReadAllProperties(newplayer);
                                    Players.Add(newplayer.Key, newplayer);
                                }

                                break;

                            case Headers.PlayerSyncData:

                                SyncDataType syncdatatype = (SyncDataType)incmsg.ReadByte();
                                if (OnReceivePlayerSyncData != null) OnReceivePlayerSyncData(playerkey, syncdatatype, incmsg);
                                break;

                            case Headers.Signal:

                                SignalType signaltype = (SignalType)incmsg.ReadByte();
                                if (signaltype == SignalType.PlayerLeft) Players.Remove(playerkey);
                                if (OnReceiveSignal != null) OnReceiveSignal(playerkey, signaltype);
                                break;
                        }

                        break;
                }

                NetClient.Recycle(incmsg);
            }
        }
    }
}
